package v1

import (
	"context"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"

	"quipus/internal/database/model"
	"quipus/internal/utils"

	v1pb "quipus/proto/gen/api/v1"
	apb "quipus/proto/gen/assist"
	cpb "quipus/proto/gen/common"
)

func (s *APIV1Service) SetKnotRelations(ctx context.Context, request *v1pb.SetKnotRelationsRequest) (*emptypb.Empty, error) {
	id, err := utils.ExtractIDFromUrn(utils.KnotUrnPrefix, request.Urn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid knot name: %v", err)
	}

	// Delete all reference relations first.
	if err := s.Gds.Handler.DeleteKnotRelation(ctx, &model.KnotRelation{
		KnotID:       id,
		InteractType: cpb.InteractType_INTERACT_UNSPECIFIED.String(),
	}); err != nil {
		return nil, status.Errorf(codes.Internal, "failed to delete knot relation")
	}

	_, err = s.Gds.Handler.GetKnot(ctx, &apb.FindKnot{Id: id})
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get knot")
	}

	for _, relation := range request.Relations {
		// Ignore reflexive relations.
		if request.Urn == relation.RelatedKnot {
			continue
		}
		// Ignore comment relations as there's no need to update a comment's relation.
		if relation.Interact == cpb.InteractType_COMMENT || relation.Interact == cpb.InteractType_CHAT {
			continue
		}

		relatedKnotID, err := utils.ExtractIDFromUrn(utils.KnotUrnPrefix, relation.RelatedKnot)
		if err != nil {
			return nil, status.Errorf(codes.InvalidArgument, "invalid related knot name: %v", err)
		}

		if err := s.Gds.Handler.UpsertKnotRelation(ctx, &model.KnotRelation{
			KnotID:        id,
			RelatedKnotID: relatedKnotID,
			InteractType:  relation.Interact.String(),
		}); err != nil {
			return nil, status.Errorf(codes.Internal, "failed to upsert knot relation")
		}
	}

	return &emptypb.Empty{}, nil
}

func (s *APIV1Service) ListKnotRelations(ctx context.Context, request *v1pb.ListKnotRelationsRequest) (*v1pb.ListKnotRelationsResponse, error) {
	id, err := utils.ExtractIDFromUrn(utils.KnotUrnPrefix, request.Urn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid knot name: %v", err)
	}
	relationList := []*v1pb.KnotRelation{}
	tempList, err := s.Gds.Handler.ListKnotRelations(ctx, &apb.FindKnotRelation{
		KnotId: id,
	})
	if err != nil {
		return nil, err
	}
	for _, relation := range tempList {
		relationList = append(relationList, convertKnotRelationFromDB(relation))
	}
	tempList, err = s.Gds.Handler.ListKnotRelations(ctx, &apb.FindKnotRelation{
		RelatedKnotId: id,
	})
	if err != nil {
		return nil, err
	}
	for _, relation := range tempList {
		relationList = append(relationList, convertKnotRelationFromDB(relation))
	}

	response := &v1pb.ListKnotRelationsResponse{
		Relations: relationList,
	}
	return response, nil
}

func convertKnotRelationFromDB(knot *model.KnotRelation) *v1pb.KnotRelation {
	return &v1pb.KnotRelation{
		Knot:        utils.BuildUrn(utils.KnotUrnPrefix, knot.KnotID),
		RelatedKnot: utils.BuildUrn(utils.KnotUrnPrefix, knot.RelatedKnotID),
		Interact:    cpb.InteractType(cpb.InteractType_value[knot.InteractType]),
	}
}
